/*
Another game of life with SDL renderer.
Copyright (C) 2009-2010  Julien CLEMENT (andematunum@gmail.com)
*/
/*
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Matrix.h"
#include "Cell.h"
#include "Utils.h"

#include <stdlib.h>
#include <iostream>

using namespace std;

Matrix::
Matrix (unsigned int w, unsigned int h, bool wrap) :
		_w(w), _h(h), _usecolors(false), _wrap(wrap) {

	int h2 = h;
	_cells = new Cell*[h];
	while (h--) {
		_cells [h] = new Cell[w];
	}

	h = h2;
	_buffer = new Cell*[h];
	while (h--) {
		_buffer [h] = new Cell[w];
	}

	_memory = _cells;

	// Build neighborhood
	for (int r=0; r < _h; r++) {
		for (int c=0; c < _w; c++) {
			// Next cell
			Cell *cell = getCell(r,c);
			
			// Build neighborhood
			cell->addNeighbor (getCell(r-1,c-1));
			cell->addNeighbor (getCell(r-1,c));
			cell->addNeighbor (getCell(r-1,c+1));
			cell->addNeighbor (getCell(r,c-1));
			cell->addNeighbor (getCell(r,c+1));
			cell->addNeighbor (getCell(r+1,c-1));
			cell->addNeighbor (getCell(r+1,c));
			cell->addNeighbor (getCell(r+1,c+1));
		}
	}

	swap();

	// Build neighborhood in buffer
	for (int r=0; r < _h; r++) {
		for (int c=0; c < _w; c++) {
			// Next cell
			Cell *cell = getCell(r,c);
			
			// Build neighborhood
			cell->addNeighbor (getCell(r-1,c-1));
			cell->addNeighbor (getCell(r-1,c));
			cell->addNeighbor (getCell(r-1,c+1));
			cell->addNeighbor (getCell(r,c-1));
			cell->addNeighbor (getCell(r,c+1));
			cell->addNeighbor (getCell(r+1,c-1));
			cell->addNeighbor (getCell(r+1,c));
			cell->addNeighbor (getCell(r+1,c+1));

			// Connect homologous cells
			swap();
			Cell * mate = getCell(r,c);
			swap();
			cell->setMate(mate);
			mate->setMate(cell);
		}
	}

	swap();
}

Matrix::
~Matrix () {
	int h2 = _h;
	while (_h--) {
		delete [] _cells[_h];
	}
	delete [] _cells;
	
	_h = h2;

	while (_h--) {
		delete [] _buffer[_h];
	}
	delete [] _buffer;

	_w			= 0;
	_h			= 0;
	_usecolors 	= false;
	_wrap       = false;
	_memory     = NULL;
}

void
Matrix::
initRandom (void) {
	for (int r=0; r<_h; r++) {
		for (int c=0; c<_w; c++) {
			_cells[r][c].initRandom();
			_buffer[r][c].die();
		}
	}
}

void
Matrix::
initRandomColor (void) {
	for (int r=0; r<_h; r++) {
		for (int c=0; c<_w; c++) {
			_cells[r][c].initRandomColor();
			_buffer[r][c].die();
		}
	}
	_usecolors = true;
}

Cell*
Matrix::
getCell (int r, int c) {
	if (((r < 0) || (c < 0) || (r >= _h) || (c >= _w)) && (_wrap == false)) {
		return NULL;
	}
	// Wrap if necessary
	if (r == -1) { r = _h-1; }
	if (r == _h) { r = 0;    }
	if (c == -1) { c = _w-1; }
	if (c == _w) { c = 0;    }

	return &_memory[r][c];
}

bool
Matrix::
life (void) {
	// The main Game Of Life algorithm
	bool changed = false;

	// Then ,make them evolve
	for (int r=0; r < _h; r++) {
		for (int c=0; c < _w; c++) {

			// Next cell
			Cell *cell = getCell(r,c);
			
			// Evolve the cell: die or get born
			if (_usecolors) {
				bool localChanged = false;
				cell->evolveColor(localChanged);
				if (localChanged) {
					changed = true;
				}
			}
			else {
				bool localChanged = false;
				cell->evolve(localChanged);
				if (localChanged) {
					changed = true;
				}
			}
		}
	}
	return changed;
}

Cell **
Matrix::
getData (void) {
	return _memory;
}

void
Matrix::
swap (void) {
	if (_memory == _cells) {
		_memory = _buffer;
	}
	else if (_memory == _buffer) {
		_memory = _cells;
	}
	else {
		cerr << "Fatal error: invalid memory buffer: " << _memory << endl;
		exit (EXIT_FAILURE);
	}
}
